use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use core::ffi::{c_char, c_void};
use core::ops::{Deref, DerefMut};

use crate::ffi::{
    cJSON_Delete, cJSON_Parse, cJSON_ParseWithLength, cJSON_Print, cJSON_free,
};
use libc::size_t;
use crate::value::Value;
use crate::{libc, Error, Result};

pub struct Json {
    value: Value,
}

impl Drop for Json {
    fn drop(&mut self) {
        unsafe { cJSON_Delete(self.value.0.as_ptr()) }
    }
}

impl ToString for Json {
    fn to_string(&self) -> String {
        if !self.value.is_null() {
            unsafe {
                let c = cJSON_Print(self.value.0.as_ptr());
                if !c.is_null() {
                    let len = libc::strlen(c);
                    let s = alloc::str::from_utf8_unchecked(core::slice::from_raw_parts(
                        c as *mut u8,
                        len as usize,
                    ))
                    .to_owned();
                    cJSON_free(c as *mut c_void);
                    return s;
                }
            }
        }
        "".to_owned()
    }
}

impl Json {
    pub fn parse_cstr(data: *const i8) -> Result<Self> {
        unsafe {
            let value = cJSON_Parse(data as *const c_char);
            if !value.is_null() {
                Ok(Self {
                    value: Value::new(value),
                })
            } else {
                Err(Error::InvalidJson)
            }
        }
    }

    pub fn parse(data: &str) -> Result<Self> {
        unsafe {
            let value = cJSON_ParseWithLength(data.as_ptr() as *const c_char, data.len() as size_t);
            if !value.is_null() {
                Ok(Self {
                    value: Value::new(value),
                })
            } else {
                Err(Error::InvalidJson)
            }
        }
    }

    pub fn new() -> Self {
        Self {
            value: Value::new_object(),
        }
    }
}

impl Deref for Json {
    type Target = Value;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl DerefMut for Json {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.value
    }
}
